// PluginManager.cs
// 
// Copyright © 2009 FreeZzaph
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace LibFreeZzaph
{
	
	/// <summary>
	/// The EventArgs given to plug-in command callbacks.
	/// </summary>
	public sealed class PluginCommandEventArgs : EventArgs
	{
		private string command;
		private IList<string> parameters;
		
		private bool handled = false;
		
		/// <value>
		/// The command that was issued
		/// </value>
		public string Command {
			get
			{
				return command;
			}
		}
		
		/// <value>
		/// The arguments given to the command
		/// </value>
		public IList<string> Parameters
		{
			get
			{
				return parameters;
			}
		}
		
		/// <value>
		/// Should be set to true by the command handler if the plug-in
		/// successfully handled the command. If not, the application using the
		/// FreeZzaph Library will likely report that an error occured.
		/// </value>
		public bool Handled
		{
			get
			{
				return handled;
			}
			set
			{
				handled = value;
			}
		}
		
		/// <summary>
		/// Initializes a new PluginCommandEventArgs class with the given
		/// command and parameters
		/// </summary>
		/// <param name="command">
		/// The command that was issued
		/// </param>
		/// <param name="parameters">
		/// The parameters the command received
		/// </param>
		internal PluginCommandEventArgs(string command, IList<string> parameters)
		{
			this.command = command;
			this.parameters = parameters;
		}
	}
	
	internal sealed class PluginManager : IPluginManager
	{
		private Dictionary<Type, Dictionary<string,PluginCommand>> commands = new Dictionary<Type,Dictionary<string,PluginCommand>>();
		
		private IDictionary<string, IFreeZzaphContentProviderPlugin> contentProviderPlugins = new Dictionary<string, IFreeZzaphContentProviderPlugin>();
		private IDictionary<string, IFreeZzaphPostProcessorPlugin> postProcessorPlugins = new Dictionary<string, IFreeZzaphPostProcessorPlugin>();
		
		public bool HandleCommand(Type type, string command, IList<string> parameters)
		{
			// Check that the type is valid
			if (type.GetInterface(typeof(IFreeZzaphPlugin).FullName) == null)
			{
				throw new ArgumentException("The given type parameter does not implement IFreeZzaphPlugin.");
			}
			
			// Check if the plug-in has any commands registered
			Dictionary<string,PluginCommand> pluginCommands;
			if (!commands.TryGetValue(type, out pluginCommands))
			{
				return false;
			}
			
			// Check if the plug-in has the given command registered
			PluginCommand pluginCommand;
			if (!pluginCommands.TryGetValue(command, out pluginCommand))
			{
				return false;
			}
			
			// Call the command
			PluginCommandEventArgs pcea = new PluginCommandEventArgs(command, parameters); 
			pluginCommand.CommandHandler((IPluginManager)this, pcea);
			
			// Return the status
			return pcea.Handled;
		}		
		
		public void RegisterCommand(Type type, string command, string description, EventHandler<PluginCommandEventArgs> commandHandler)
		{
			Dictionary<string,PluginCommand> typeCommands;
			
			// Check that the type is valid
			if (type.GetInterface(typeof(IFreeZzaphPlugin).FullName) == null)
			{
				throw new ArgumentException("The given type does not implement IFreeZzaphPlugin.");
			}
			
			if (commands.ContainsKey(type))
			    typeCommands = commands[type];
			else
			{
				typeCommands = new Dictionary<string,PluginCommand>();
				commands.Add(type, typeCommands);
			}
			
			typeCommands.Add(command, new PluginCommand(command, description, commandHandler));
		}
		
		public IDictionary<string, IFreeZzaphContentProviderPlugin> ContentProviderPlugins
		{
			get
			{
				return contentProviderPlugins;
			}
		}
		
		public IDictionary<string, IFreeZzaphPostProcessorPlugin> PostProcessorPlugins
		{
			get
			{
				return postProcessorPlugins;
			}
		}
		
		public void LoadPlugins(string pluginDirectory)
		{
			string[] pluginFiles;
			List<LoadPluginException> loadExceptions = new List<LoadPluginException>();
			
			// First, check if we can access the plug-in directory
			try
			{
				 pluginFiles = Directory.GetFiles(pluginDirectory, "*.dll");
			}
			catch (Exception e)
			{
				throw new LoadPluginException(
					string.Format("Could not access the plug-in directory '{0}'. Cannot load any plug-ins.", pluginDirectory), e);
			}
			
			foreach (string pluginFile in pluginFiles)
			{
				// Load the assembly
				Assembly assembly;
				try
				{
					assembly = Assembly.LoadFrom(pluginFile);
				}
				catch (Exception e)
				{
					loadExceptions.Add(new LoadPluginException(
						string.Format("Could not load plug-in assembly '{0}'.", pluginFile), e));
					continue;
				}
				
				// Get the assembly's types
				Type[] types;
				try
				{
					types = assembly.GetTypes();
				}
				catch (Exception e)
				{
					loadExceptions.Add(new LoadPluginException(
						string.Format("Could not load plug-in assembly '{0}'.", pluginFile), e));
					continue;
				}
				
				// Iterate over these types and attempt to load the ones that
				// are IFreeZzaphPlugins
				foreach (Type type in types)
				{
					try
					{
						// Check if the current type is of IFreeZzaphPlugin type.
						Type interfaceMatch = type.GetInterface(typeof(IFreeZzaphPlugin).FullName);
						
						if (interfaceMatch != null)
						{
							// Create an instance of the plug-in
							IFreeZzaphPlugin plugin = assembly.CreateInstance(type.FullName) as IFreeZzaphPlugin;
							
							// Now figure out what kind of plug-in it is
							if (plugin is IFreeZzaphContentProviderPlugin)
							{
								IFreeZzaphContentProviderPlugin contentProviderPlugin = (IFreeZzaphContentProviderPlugin)plugin;
								contentProviderPlugin.Initialize(this);
								contentProviderPlugins.Add(plugin.Name, contentProviderPlugin);
							}
							else if (plugin is IFreeZzaphPostProcessorPlugin)
							{
								IFreeZzaphPostProcessorPlugin postProcessorPlugin = (IFreeZzaphPostProcessorPlugin)plugin;
								postProcessorPlugins.Add(plugin.Name, postProcessorPlugin);
							}
							else
							{
								loadExceptions.Add(new LoadPluginException(
									string.Format("{0} implements IFreeZzaphPlugin, but a more specific plug-in type was expected.", type.FullName)));
							}
						}
					}
					catch (Exception e)
					{
						loadExceptions.Add(new LoadPluginException(
							string.Format("An exception occured when attempting to create an instance of {0}.", type.FullName), e));
					}
				}
			}
			
			if (loadExceptions.Count != 0)
			{
				throw new LoadPluginException("Exceptions occured while loading the plug-ins. See the ExceptionList.", loadExceptions);
			}
		}
	}
}
